package milkyway.hightech.banking;

import java.io.IOException;

import java.math.BigDecimal;
import java.math.BigInteger;

import java.util.Collection;
import java.util.Comparator;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Observable;
import java.util.Observer;
import java.util.ResourceBundle;
import java.util.logging.Logger;

import milkyway.hightech.admin.BankAdministrationView;
import milkyway.hightech.b2b.B2B;
import milkyway.hightech.b2b.BankRegistry;
import milkyway.hightech.comparators.SortByAmount;
import milkyway.hightech.comparators.SortByDate;
import milkyway.hightech.comparators.SortByName;
import milkyway.hightech.comparators.SortOrder;
import milkyway.hightech.conditions.GiroAccountCond;
import milkyway.hightech.conditions.GiroCondTypes;
import milkyway.hightech.conditions.InvAccountCond;
import milkyway.hightech.conditions.InvCondTypes;
import milkyway.hightech.customer.BankCustomerView;
import milkyway.hightech.entry.AccountStatementService;
import milkyway.hightech.entry.Entry;
import milkyway.hightech.exceptions.AccountNotExpiredException;
import milkyway.hightech.exceptions.BankNotFountException;
import milkyway.hightech.exceptions.NotEnoughMoneyException;
import milkyway.hightech.exceptions.SystemNotAvailableException;
import milkyway.hightech.exceptions.TransactionNotAllowedException;
import milkyway.hightech.formatter.FormatterTypes;
import milkyway.hightech.money.Currency;
import milkyway.hightech.money.Money;
import milkyway.hightech.timer.TimeEmitter;

/**
 *
 * @author michael
 */
public class BankImpl implements BankCustomerView, Observer, B2B,
    BankAdministrationView
{

    /**
     *
     */
    private final String bankName;

    /**
     *
     */
    private ResourceBundle rb;

    /**
     * the initial number of giro accounts
     */
    private int accountNumber = 0;

    /**
     *
     */
    private Map<Integer, Account> accounts = new HashMap<Integer, Account>();
    
    /**
     *
     */
    private EnumMap<InvCondTypes, InvAccountCond> invCond =
        new EnumMap<InvCondTypes, InvAccountCond>(InvCondTypes.class);
    
    /**
     * 
     */
    private EnumMap<GiroCondTypes, GiroAccountCond> giroCond =
        new EnumMap<GiroCondTypes, GiroAccountCond>(GiroCondTypes.class);

    /**
     *
     */
    private EnumMap<SortOrder, Comparator<Entry>> comparators =
        new EnumMap<SortOrder, Comparator<Entry>>(SortOrder.class);

    /**
     *
     */
    private TimeEmitter timer = TimeEmitter.getInstance();

    /**
     *
     */
    private AccountStatementService statementService;

    /**
     *
     */
    private static final Logger logger =
        Logger.getLogger(BankImpl.class.getName());

    /**
     *
     * @param bankName
     * @param locale
     * @throws ClassNotFoundException
     * @throws InstantiationException
     * @throws IllegalAccessException
     */
    public BankImpl(String bankName, Locale locale)
        throws ClassNotFoundException, InstantiationException,
            IllegalAccessException
    {
        this.bankName = bankName;

        BankRegistry.getInstance().bind(bankName, this);

        timer.addObserver(this);

        this.rb = ResourceBundle.getBundle("milkyway.hightech.locales.Locale",
            locale);
        
        statementService = new AccountStatementService(rb);
        
        GiroCondTypes giroCondType = GiroCondTypes.GROUP1;
        giroCond.put(giroCondType, new GiroAccountCond(14, giroCondType, rb));
        
        giroCondType = GiroCondTypes.GROUP2;
        giroCond.put(giroCondType, new GiroAccountCond(14, giroCondType, rb));
        
        giroCondType = GiroCondTypes.GROUP3;
        giroCond.put(giroCondType, new GiroAccountCond(16, giroCondType, rb));

        InvCondTypes invCondType = InvCondTypes.THREE_MONTH;
        invCond.put(invCondType, new InvAccountCond(1.5, invCondType, rb));
        
        invCondType = InvCondTypes.SIX_MONTH;
        invCond.put(invCondType, new InvAccountCond(2.0, invCondType, rb));
        
        invCondType = InvCondTypes.NINE_MONTH;
        invCond.put(invCondType, new InvAccountCond(2.5, invCondType, rb));
        
        invCondType = InvCondTypes.TWELVE_MONTH;
        invCond.put(invCondType, new InvAccountCond(3.0, invCondType, rb));

        comparators.put(SortOrder.Name, new SortByName());
        comparators.put(SortOrder.Date, new SortByDate());
        comparators.put(SortOrder.Amount, new SortByAmount());

        timer.startTiming();
    }

    /**
     *
     * @return
     */
    public String getBankName()
    {
        return bankName;
    }

    /**
     *
     * @param o
     * @param obj
     */
    @Override
    public void update(Observable o, Object obj)
    {
        if (obj instanceof GiroAccount)
        {
            payInterest(((Account)obj).getAccountNumber());
        }
        
        else if (obj instanceof InvestmentAccount)
        {
            ((InvestmentAccount)obj).renewContract();
        }
    }

    /***************************************************************************
     * Interface BankCustomerView
     **************************************************************************/

    /**
     *
     * @param accountHolder
     * @param conditionType
     * @return
     */
    @Override
    public String openInvestmentAccount(String accountHolder,
        InvCondTypes conditionType)
    {
        accountNumber = accounts.hashCode();
        
        Currency currency = (Currency)rb.getObject("currency");
        
        Account account = new InvestmentAccount(accountNumber, accountHolder,
            currency, getInvCondition(conditionType), timer.getCalendar());
        
        accounts.put(accountNumber, account);

        return rb.getObject("openAccountOK").toString()
               + Integer.toString(accountNumber);
    }

    /**
     *
     * @param accountHolder
     * @param conditionType
     * @return
     */
    @Override
    public String openGiroAccount(String accountHolder,
        GiroCondTypes conditionType)
    {
        accountNumber = accounts.hashCode();
        
        Currency currency = (Currency)rb.getObject("currency");
        
        Account account = new GiroAccount(accountNumber, accountHolder,
            currency, getGiroCondition(conditionType), timer.getCalendar());
        
        accounts.put(accountNumber, account);

        return rb.getObject("openAccountOK").toString()
               + Integer.toString(accountNumber);
    }

    /**
     *
     * @param accountNumber
     * @return
     */
    @Override
    public String getAccountBalance(Integer accountNumber)
    {
        try
        {
            Account account = accounts.get(accountNumber);
            BigDecimal d = new BigDecimal(account.getAccountBalance());
            d = d.divide(new BigDecimal("100"));
            
            Object ballance = rb.getObject("accountBalance");
            Object currency = rb.getObject("currency");
            
            return ballance + d.toString() + " " + currency;
        }
        
        catch (NullPointerException e)
        {
            Object wrongAccount = rb.getString("wrongAccountNumber");
            throw new SystemNotAvailableException(wrongAccount.toString());
        }
    }

   /**
     *
     * @param accountNumber
     * @param amount
     * @return
     */
    @Override
    public String payInMoney(Integer accountNumber, BigInteger amount)
    {
        try
        {
            Account account = accounts.get(accountNumber);
            BigInteger value = amount.multiply(new BigInteger("100"));
            Money money = new Money(value, account.getCurrency());
            String reason = rb.getObject("payInMoney").toString();
            
            account.enterOnCredit(money, reason);
            
            String message = rb.getObject("payInMoneyOK").toString();
            String currency = rb.getObject("currency").toString();
            
            return message + amount.toString() + " " + currency;
        }
        
        catch (NullPointerException e)
        {
            logger.info(e.getMessage());
            String message = rb.getObject("accountNotFound").toString();
            throw new SystemNotAvailableException(message);
        }

        catch (AccountNotExpiredException e)
        {
            logger.info(e.getMessage());
            String message = rb.getObject("accountNotExpired").toString();
            throw new SystemNotAvailableException(message);
        }

        catch (SystemNotAvailableException e)
        {
            logger.info(e.getMessage());
            throw new SystemNotAvailableException();
        }
    }

    /**
     *
     * @param accountNumber
     * @param amount
     * @param currency
     * @param reasonOfTransfer
     * @return
     */
    @Override
    public String payInMoney(Integer accountNumber, BigInteger amount,
        Currency currency, String reasonOfTransfer)
    {
        try
        {
            Money m = new Money(amount.multiply(new BigInteger("100")), currency);
            
            accounts.get(accountNumber).enterOnCredit(m, reasonOfTransfer);
            
            String message = rb.getObject("payInMoneyOK").toString();
            String strCurrency = rb.getObject("currency").toString();
            
            return message + amount.toString() + " " + strCurrency;
        }
        
        catch (NullPointerException e)
        {
            logger.info(e.getMessage());
            String message = rb.getObject("accountNotFound").toString();
            throw new SystemNotAvailableException(message);
        }

        catch (AccountNotExpiredException e)
        {
            logger.info(e.getMessage());
            String message = rb.getObject("accountNotExpired").toString();
            throw new SystemNotAvailableException(message);
        }

        catch (SystemNotAvailableException e)
        {
            logger.info(e.getMessage());
            throw new SystemNotAvailableException();
        }
    }

    /**
     *
     * @param accountNumber
     * @param amount
     * @return
     */
    @Override
    public String drawMoney(Integer accountNumber, BigInteger amount)
    {
        try
        {
            Account account = accounts.get(accountNumber);
            BigInteger value = amount.multiply(new BigInteger("100"));
            Money money = new Money(value, account.getCurrency());
            
            account.enterOnDebit(money, rb.getObject("drawMoney").toString());
            
            return rb.getObject("drawMoneyOK").toString();
        } 
        
        catch (SystemNotAvailableException e)
        {
            logger.info(e.getMessage());
            throw new SystemNotAvailableException();
        }

        catch (AccountNotExpiredException e)
        {
            logger.info(e.getMessage());
            String message = rb.getObject("accountNotExpired").toString();
            throw new SystemNotAvailableException(message);
        }

        catch (TransactionNotAllowedException e)
        {
            logger.info(e.getMessage());
            String message = rb.getObject("notEnoughMoney").toString();
            throw new SystemNotAvailableException(message);
        }

        catch (NotEnoughMoneyException e)
        {
            logger.info(e.getMessage());
            String message = rb.getObject("notEnoughMoney").toString();
            throw new SystemNotAvailableException(message);
        }

        catch (NullPointerException e)
        {
            logger.info(e.getMessage());
            String message = rb.getObject("accountNotFound").toString();
            throw new SystemNotAvailableException(message);
        }
    }

    /**
     *
     * @param bank
     * @param sourceAccount
     * @param targetAccount
     * @param amount
     * @param currency
     * @param reasonOfTransfer
     * @return
     */
    @Override
    public String transferMoney(String bank, Integer sourceAccount,
                                Integer targetAccount, BigInteger amount,
                                Currency currency, String reasonOfTransfer)
    {
        try
        {
            Account account;
            BigInteger value = amount.multiply(new BigInteger("100"));
            Money money = new Money(value, currency);

            account = accounts.get(sourceAccount);
            account.enterOnDebit(money, reasonOfTransfer);

            if (bank != null || !bank.equals(""))
            {
                BankRegistry reg = BankRegistry.getInstance();
                reg.lookup(bank).payInMoney(targetAccount, amount,
                    currency, reasonOfTransfer);
            }
            else
            {
                account = accounts.get(targetAccount);
                account.enterOnCredit(money, reasonOfTransfer);
            }

            return rb.getObject("transferOK").toString();
        }

        catch (BankNotFountException e)
        {
            logger.info(e.getMessage());
            String message = rb.getObject("bankNotFound").toString();
            throw new SystemNotAvailableException(message);
        }
    }

    /**
     *
     * @param accountNumber
     * @param fileName
     * @param mimeType
     * @param sortOrder
     * @return
     */
    @Override
    public Object[] getAccountStatement(Integer accountNumber, String fileName,
        FormatterTypes mimeType, SortOrder sortOrder)
    {
        Account account = accounts.get(accountNumber);
        account.sortList(comparators.get(sortOrder));

        try
        {
            statementService.getStatement(account.getIterator(), fileName,
                mimeType, rb);
            
            Object[] result = new Object[2];
            result[0] = rb.getObject("getStatementOK").toString();
            result[1] = fileName + mimeType.getMimeType(mimeType);
            
            return result;
        }
        
        catch (IOException e)
        {
            logger.fine(e.getMessage());
            throw new SystemNotAvailableException();
        }
    }

    /**
     *
     * @return
     */
    @Override
    public String getConditions()
    {
        Collection<InvAccountCond> investment = invCond.values();
        
        Collection<GiroAccountCond> giro = giroCond.values();
        
        Iterator<InvAccountCond> itr1 = investment.iterator();
        
        Iterator<GiroAccountCond> itr2 = giro.iterator();

        String condition =
            rb.getObject("investmentConditions").toString() + "\n\n";

        while (itr1.hasNext())
            condition += itr1.next().toString() + "\n";

        for (int i = 0; i < 60; i++)
            condition += "-";

        condition += "\n";
        condition += rb.getObject("giroConditions").toString() + "\n\n";
        itr2 = giro.iterator();

        while (itr2.hasNext())
            condition += itr2.next().toString() + "\n";

        return condition;
    }
    
    /**
     *
     * @param accountNumber
     */
    @Override
    public void payInterest(int accountNumber)
    {
        try
        {
            accounts.get(accountNumber).payInterest();
        }
        
        catch (NoSuchElementException e)
        {
            logger.fine(e.getMessage());
        }
        
        catch (TransactionNotAllowedException e)
        {
            logger.fine(e.getMessage());
        }
    }

    /**
     *
     * @param condType
     * @param interest
     * @param contractPeriod
     */
    @Override
    public void addInvCond(InvCondTypes condType,
        double interest, int contractPeriod)
    {
        invCond.put(condType, new InvAccountCond(interest, condType, rb));
    }
    
    /**
     *
     * @param conditionType
     * @return
     */
    public InvAccountCond getInvCondition(InvCondTypes conditionType)
    {
        return invCond.get(conditionType);
    }

    /**
     *
     * @param condType
     * @param cond
     */
    @Override
    public void changeGiroInterest(GiroCondTypes condType, GiroAccountCond cond)
    {
        giroCond.put(condType, cond);
    }
    
    /**
     *
     * @param conditionType
     * @return
     */
    public GiroAccountCond getGiroCondition(GiroCondTypes conditionType)
    {
        return giroCond.get(conditionType);
    }

    /**
     * list all accounts.
     */
    @Override
    public void list()
    {
        Collection<Account> collection = accounts.values();

        while (collection.iterator().hasNext())
        {
            System.out.println(collection.iterator().next().toString());
        }
    }

    /**
     *
     * @return the resourcebundle of this instance.
     */
    public ResourceBundle getRb()
    {
        return rb;
    }
}
